
//
// 20120825 - AH - Created.
// 20120905 - AH - assertEquals(Object, Object), assertNotNull(Object) and 
//                 assertNull(Object).
// 20120906 - AH - assertToString(String, Object).
// 20120908 - AH - assert methods that accepts name argument.
// 20120909 - AH - assertConcreteType(Class<?>, Object) and
//                 assertConcreteType(String, Class<?>, Object).
// 20120929 - AH - assertTrue(boolean) and assertTrue(String, boolean).
// 20121001 - AH - asserts for float and double values.
// 20121010 - AH - setWorld(Object) now throws exception if object not root.
// 20121013 - AH - Renamed from TestTree.
// 20121104 - AH - setIgnoreAssertion(boolean) and getIgnoreAssertion().
// 20121110 - AH - Fixed that test said had performed when had failed.
// 20121111 - AH - reset() and resetTree().
// 20121113 - AH - pathToString(JhutTest[]).
//

package org.japril.jhut.framework;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * <p>
 *   Node in a test tree.
 * </p>
 * @author Arne Halvorsen (AH) 
 */
public class JhutTest
{
  private String m_name="unnamed";
  
  private Object m_world=null;
  
  private JhutTest m_parent=null;
  
  private final List<JhutTest> m_children=new ArrayList<JhutTest>();
  
  private boolean m_performed=false;
  
  private Throwable m_error=null;
  
  /**
   * <p>
   *   Constructor for root.
   * </p>
   */
  public JhutTest(){}
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * @param name Name of test.
   */
  public JhutTest(String name){ setName(name); }
  
  /**
   * <p>
   *   Constructor for root.
   * </p>
   * @param world World.
   */
  public JhutTest(Object world){ setWorld(world); }
  
  /**
   * <p>
   *   Constructor for root.
   * </p>
   * @param name  Name of test. 
   * @param world World.
   */
  public JhutTest(String name, Object world){ setName(name); setWorld(world); }
  
  /**
   * <p>
   *   Sets world.
   * </p>
   * @param world World.
   * @throws IllegalStateException If {@code this} is not a root.
   */
  public final void setWorld(Object world)
  {
    if (world==null)
    {
      throw new NullPointerException("world");
    }
    
    if (!isRoot())
    {
      throw new IllegalStateException("this is not a root");
    }
    
    m_world=world;
  }
  
  /**
   * <p>
   *   Gets world.
   * </p>
   * @return World or {@code null} if no world found.
   */
  public final Object getWorld()
  { 
    if (m_world!=null) return m_world;
    
    return (m_parent==null) ? null : m_parent.getWorld();
  }
  
  /**
   * <p>
   *   Sets name of test.
   * </p>
   * @param name Name.
   */
  public final void setName(String name)
  {
    name=(name==null) ? "" : name.trim();
    m_name=(name.length()==0) ? "unammed" : name;
  }
  
  /**
   * <p>
   *   Gets name of test.
   * </p>
   * @return Name.
   */
  public final String getName(){ return m_name; }
  
  /**
   * <p>
   *   Performs test at this node.
   * </p>
   * @param tc {@link TestContext}.
   */
  public final void performTest(TestContext tc)
  {
    if (performed()) return;
    
    tc.log("Performs test: "+getName());
    
    m_performed=false;
    
    try
    {
      test(tc);
      m_performed=true;
    }
    catch (Throwable t)
    {
      m_error=t;
      t.printStackTrace();
    }
  }
  
  /**
   * <p>
   *   Reset test in 'not performed state'.
   * </p>
   */
  public final void reset(){ m_error=null; m_performed=false; }
  
  /**
   * <p>
   *   Sets tests in the sub tree {@code this} is the root in to not performed
   *   state.
   * </p>
   */
  public final void resetTree()
  {
    reset();
    for (JhutTest child : m_children) child.resetTree();
  }
  
  /**
   * <p>
   *   Performs test.
   * </p>
   * @param tc {@link TestContext}. 
   * @throws Throwable If fails.
   */
  protected void test(TestContext tc) throws Throwable {};
  
  /**
   * <p>
   *   Tells if test performed.
   * </p>
   * @return {@code true} if else {@code false}.
   */
  public final boolean performed(){ return m_performed; }
  
  /**
   * <p>
   *   Tells if test performed OK.
   * </p>
   * @return {@code true} if else {@code false}.
   */
  public final boolean ok(){ return performed() && m_error==null; }
  
  /**
   * <p>
   *   Gets error test produced.
   * </p>
   * @return Error or {@code null} if not performed or performed OK.
   */
  public final Throwable getError(){ return m_error; }
  
  /**
   * <p>
   *   Tells if {@code this} is the root.
   * </p>
   * @return {@code true} if is, {@code false} else.
   */
  public boolean isRoot(){ return m_parent==null; }
  
  /**
   * <p>
   *   Gets number of children.
   * </p>
   * @return Count.
   */
  public int getChildCount(){ return m_children.size(); }
  
  /**
   * <p>
   *   Gets child.
   * </p>
   * @param idx Index of child.
   * @return Child.
   * @throws IndexOutOfBoundsException If {@code idx<0 || idx>=size()}.
   */
  public JhutTest getChild(int idx){ return m_children.get(idx); }
  
  /**
   * <p>
   *   Gets index of child.
   * </p>
   * @param child Child.
   * @return Index or {@code -1} if not found.
   */
  public int getIndexOfChild(Object child)
  {
    if (child==null)
    {
      throw new NullPointerException("child");
    }
    
    return m_children.indexOf(child);
  }
  
  /**
   * <p>
   *   Tells if {@code this} is a leaf.
   * </p>
   * @return {@code true} if is, {@code false} else.
   */
  public boolean isLeaf(){ return getChildCount()==0; }
  
  /**
   * <p>
   *   Adds child.
   * </p>
   * @param child Child to add.
   * @throws IllegalArgumentException If {@code child} has parent.
   */
  public void addChild(JhutTest child)
  {
    if (child==null)
    {
      throw new NullPointerException("child");
    }
    
    if (child.m_parent!=null)
    {
      throw new IllegalArgumentException("has parent");
    }
    
    m_children.add(child);
    child.m_parent=this;
  }
  
  /**
   * <p>
   *   Gets path from {@code this} to (including) root.
   * </p>
   * @return Path.
   */
  public JhutTest[] getPath()
  {
    Stack<JhutTest> s=new Stack<JhutTest>();
    
    JhutTest curr=this;
    while (curr!=null)
    {
      s.push(curr);
      curr=curr.m_parent;
    }
    
    int n=s.size();
    JhutTest[] retVal=new JhutTest[n];
    int i=0;
    while (!s.isEmpty()) retVal[i++]=s.pop();
    return retVal;
  }
  
  /**
   * <p>
   *   Gets 
   *   {@link String} presentation of path.
   * </p>
   * @param path Path to get presentation off.
   * @return Compiled.
   */
  public static String pathToString(JhutTest[] path)
  {
    if (path==null)
    {
      throw new NullPointerException("path");
    }
    
    int n=path.length;
    
    if (n==0) return "";
    
    StringBuilder sb=new StringBuilder();
    
    for (int i=0; i<n; i++)
    {
      JhutTest curr=path[i];
      if (curr==null)
      {
        throw new NullPointerException("path["+i+"]");
      }
      
      if (i>0) sb.append('/');
      sb.append(curr.getName());
    }
    
    return sb.toString();
  }
  
  /**
   * <p>
   *   Visit in depth first search order.
   * </p>
   * @param v Visitor.
   */
  public void dfs(TestTreeVisitor v)
  {
    if (v==null)
    {
      throw new NullPointerException("v");
    }
    
    Stack<JhutTest> stack=new Stack<JhutTest>();
    stack.push(this);
    
    while (!stack.isEmpty())
    {
      JhutTest visited=stack.pop();
      if (!v.visit(visited)) return;
      
      int n=visited.getChildCount();
      for (int i=0; i<n; i++)
      {
        stack.push(visited.getChild(i));
      }
    }
  }
  
  @Override
  public String toString(){ return m_name; }
  
  /**
   * <p>
   *   The empty array shared.
   * </p>
   */
  public static final JhutTest[] EMPTY_ARRAY=new JhutTest[0];
  
}
